{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Add Groundedness Check\n",
    "\n",
    "- Author: [JeongGi Park](https://github.com/jeongkpa)\n",
    "- Peer Review: \n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/02-Structures/03-LangGraph-Add-Groundedness-Check.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/02-Structures/03-LangGraph-Add-Groundedness-Check.ipynb)\n",
    "## Overview\n",
    "\n",
    "\n",
    "In this tutorial, we perform a Naive Retrieval-Augmented Generation (RAG) step and then add a relevance check (Groundedness Check) to evaluate how relevant the retrieved documents are for answering the question.\n",
    "\n",
    "\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Basic PDF-based Retrieval Chain](#basic-pdf-based-retrieval-chain)\n",
    "- [Defining State](#defining-state)\n",
    "- [Degining Nodes](#degining-nodes)\n",
    "- [Defining Edges](#defining-edges)\n",
    "- [Running a graph](#running-a-graph)\n",
    "\n",
    "### References\n",
    "- [langgraph](https://langchain-ai.github.io/langgraph/)\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial -U"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langsmith\",\n",
    "        \"langchain-openai\",\n",
    "        \"langchain\",\n",
    "        \"python-dotenv\",\n",
    "        \"langchain-core\",     \n",
    "        \"langchain-opentutorial\"\n",
    "\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"Add Groundedness Check\",\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[Note] If you are using a ```.env``` file, proceed as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic PDF-based Retrieval Chain\n",
    "\n",
    "In this section, we create a simple retrieval chain based on PDF documents. \n",
    "\n",
    "We separate the Retriever and Chain so that we can customize each node within LangGraph.\n",
    "\n",
    "![](./assets/03-langgraph-add-relevance-check.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PDF-based retrieval chain created.\n"
     ]
    }
   ],
   "source": [
    "from rag.base import RetrievalChain\n",
    "from langchain_community.document_loaders import PDFPlumberLoader\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "from typing import List, Annotated\n",
    "\n",
    "\n",
    "class PDFRetrievalChain(RetrievalChain):\n",
    "    def __init__(self, source_uri: Annotated[str, \"Source URI\"]):\n",
    "        self.source_uri = source_uri\n",
    "        self.k = 10\n",
    "\n",
    "    def load_documents(self, source_uris: List[str]):\n",
    "        docs = []\n",
    "        for source_uri in source_uris:\n",
    "            loader = PDFPlumberLoader(source_uri)\n",
    "            docs.extend(loader.load())\n",
    "\n",
    "        return docs\n",
    "\n",
    "    def create_text_splitter(self):\n",
    "        return RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50)\n",
    "\n",
    "# Create a PDF-based retrieval chain\n",
    "pdf_chain_builder = PDFRetrievalChain([\n",
    "    \"./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf\"  # Example PDF document\n",
    "])\n",
    "\n",
    "# The chain has two main components: retriever and chain\n",
    "pdf = pdf_chain_builder.create_chain()\n",
    "\n",
    "pdf_retriever = pdf.retriever\n",
    "pdf_chain = pdf.chain\n",
    "\n",
    "print(\"PDF-based retrieval chain created.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining State\n",
    "\n",
    "We define a ```GraphState``` that represents the shared state among nodes. \n",
    "\n",
    "It's typically a Python ```TypedDict```. In this tutorial, the state includes a new key relevance to store the relevance check result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GraphState defined.\n"
     ]
    }
   ],
   "source": [
    "from typing import Annotated, TypedDict\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "class GraphState(TypedDict):\n",
    "    question: Annotated[str, \"Question\"]  # The user's question\n",
    "    context: Annotated[str, \"Context\"]    # The retrieved document context\n",
    "    answer: Annotated[str, \"Answer\"]      # The final answer\n",
    "    messages: Annotated[list, add_messages] # The conversation messages\n",
    "    relevance: Annotated[str, \"Relevance\"] # The relevance check (e.g., \"yes\" or \"no\")\n",
    "\n",
    "print(\"GraphState defined.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Degining Nodes\n",
    "\n",
    "Nodes perform each step in the graph. Each node is implemented as a Python function that:\n",
    "\n",
    "1. Takes the current ```GraphState``` as input.\n",
    "2. Performs certain logic.\n",
    "3. Returns an updated ```GraphState```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Nodes defined.\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_opentutorial.evaluator import GroundednessChecker\n",
    "from langchain_opentutorial.messages import messages_to_history\n",
    "\n",
    "def format_docs(docs):\n",
    "    return \"\\n\".join(\n",
    "        [\n",
    "            f\"<document><content>{doc.page_content}</content><source>{doc.metadata['source']}</source><page>{int(doc.metadata['page'])+1}</page></document>\"\n",
    "            for doc in docs\n",
    "        ]\n",
    "    )\n",
    "\n",
    "# Document retrieval node\n",
    "def retrieve_document(state: GraphState) -> GraphState:\n",
    "    # Get the latest question from state\n",
    "    latest_question = state[\"question\"]\n",
    "    \n",
    "    # Retrieve relevant documents from the PDF retriever\n",
    "    retrieved_docs = pdf_retriever.invoke(latest_question)\n",
    "    \n",
    "    # Format the retrieved documents for prompt usage\n",
    "    retrieved_docs_formatted = format_docs(retrieved_docs)\n",
    "    \n",
    "    # Store the formatted documents into the state context\n",
    "    return {\n",
    "        **state,\n",
    "        \"context\": retrieved_docs_formatted\n",
    "    }\n",
    "\n",
    "# Answer generation node\n",
    "def llm_answer(state: GraphState) -> GraphState:\n",
    "    # Get the latest question from state\n",
    "    latest_question = state[\"question\"]\n",
    "    # Get the retrieved documents from state\n",
    "    context = state[\"context\"]\n",
    "\n",
    "    # Generate answer using the chain\n",
    "    response = pdf_chain.invoke(\n",
    "        {\n",
    "            \"question\": latest_question,\n",
    "            \"context\": context,\n",
    "            \"chat_history\": messages_to_history(state[\"messages\"]),\n",
    "        }\n",
    "    )\n",
    "\n",
    "    # Update the state's answer and messages\n",
    "    return {\n",
    "        **state,\n",
    "        \"answer\": response,\n",
    "        \"messages\": state[\"messages\"] + [(\"user\", latest_question), (\"assistant\", response)],\n",
    "    }\n",
    "\n",
    "# Relevance check node\n",
    "def relevance_check(state: GraphState) -> GraphState:\n",
    "    # Create a relevance evaluator\n",
    "    question_retrieval_relevant = GroundednessChecker(\n",
    "        llm=ChatOpenAI(model=\"gpt-4o-mini\", temperature=0), \n",
    "        target=\"question-retrieval\"\n",
    "    ).create()\n",
    "\n",
    "    # Perform relevance check (expected to return \"yes\" or \"no\")\n",
    "    response = question_retrieval_relevant.invoke({\n",
    "        \"question\": state[\"question\"],\n",
    "        \"context\": state[\"context\"]\n",
    "    })\n",
    "\n",
    "    print(\"==== [RELEVANCE CHECK] ====\\n\", response.score)\n",
    "\n",
    "    return {\n",
    "        **state,\n",
    "        \"relevance\": response.score\n",
    "    }\n",
    "\n",
    "def is_relevant(state: GraphState) -> str:\n",
    "    if state[\"relevance\"] == \"yes\":\n",
    "        return \"relevant\"\n",
    "    else:\n",
    "        return \"not relevant\"\n",
    "\n",
    "print(\"Nodes defined.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Defining Edges\n",
    "\n",
    "Edges define which node is executed next, based on the current ```GraphState```. \n",
    "\n",
    "We can create conditional edges to handle different logic flows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Graph compiled.\n"
     ]
    }
   ],
   "source": [
    "from langgraph.graph import END, StateGraph\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "\n",
    "# Create a graph with GraphState\n",
    "workflow = StateGraph(GraphState)\n",
    "\n",
    "# Add nodes\n",
    "workflow.add_node(\"retrieve\", retrieve_document)\n",
    "workflow.add_node(\"relevance_check\", relevance_check)\n",
    "workflow.add_node(\"llm_answer\", llm_answer)\n",
    "\n",
    "# Add edges\n",
    "workflow.add_edge(\"retrieve\", \"relevance_check\")\n",
    "\n",
    "# Add conditional edges from relevance_check to either llm_answer or retrieve again\n",
    "workflow.add_conditional_edges(\n",
    "    \"relevance_check\",\n",
    "    is_relevant,\n",
    "    {\n",
    "        \"relevant\": \"llm_answer\",\n",
    "        \"not relevant\": \"retrieve\"\n",
    "    },\n",
    ")\n",
    "\n",
    "# End after generating answer\n",
    "workflow.add_edge(\"llm_answer\", END)\n",
    "\n",
    "# Set the entry point\n",
    "workflow.set_entry_point(\"retrieve\")\n",
    "\n",
    "# Initialize a memory checkpoint\n",
    "memory = MemorySaver()\n",
    "\n",
    "# Compile the graph\n",
    "app = workflow.compile(checkpointer=memory)\n",
    "\n",
    "print(\"Graph compiled.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Visualizing the Graph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Graph visualization displayed.\n"
     ]
    }
   ],
   "source": [
    "### Visualizing the Graph\n",
    "\n",
    "from langchain_opentutorial.graphs import visualize_graph\n",
    "\n",
    "visualize_graph(app)\n",
    "print(\"Graph visualization displayed.\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running a graph\n",
    "\n",
    "\n",
    "The ```config``` parameter passes configuration information required when running the graph.\n",
    "```recursion_limit```: Sets the maximum number of recursions when running the graph.\n",
    "```inputs```: Passes input information required when running the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== [RELEVANCE CHECK] ====\n",
      " yes\n",
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mrelevance_check\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "\u001b[1;32mquestion\u001b[0m:\n",
      "What are the three main principles of the European Union's approach to ensuring \"trustworthy AI,\" and why are they significant?\n",
      "\u001b[1;32mcontext\u001b[0m:\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "laws and regulation. Some negative examples have been given order to achieve ‘trustworthy AI’, three components are necessary:</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>5</page></document>\n",
      "<document><content>(Teich & Tirias Research, 2018). Such examples are fuelling a va- trustworthy5. These policies culminated in the White Paper on AI\n",
      "riety of concerns about accountability, fairness, bias, autonomy, – A European Approach to Excellence and Trust (European Com-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>5</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "data for innovators, particularly in the business-to-business (B2B) 4 https://ec.europa.eu/digital-single-market/en/news/ethics-guidelines-trustworthy-ai\n",
      "or government-to-citizens (G2C) domains: e.g. by open access to</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>6</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "CONSISTENCY, RELIABILITY, AND TRANS-\n",
      "PARENCY: ALL CONTEXT-DEPENDENT\n",
      "Many AI innovations are still emerging and in experimental phase,\n",
      "and as such they still have to prove their consistency and relia-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>7</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "INTRODUCTION\n",
      "While a clear cut definition of Artificial Intelligence (AI) would be lives of EU citizens and bring major benefits to society and eco-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>4</page></document>\n",
      "<document><content>and mitigating risks and ensuring human rights and European va- with in the Health sector a system close to the current stepwise\n",
      "lues. approval processes for medicines and equipment.\n",
      "In addition, the following principles contribute to increase the po-\n",
      "sitive impact of AI applications:</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>21</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "in order to avoid biased decision and ensure fairness, we need to Current policy challenges and debates, besides geopolitical dis-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>8</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "CONTENT\n",
      "EXECUTIVE SUMMARY 3\n",
      "INTRODUCTION\n",
      "CONSISTENCY, RELIABILITY, AND TRANSPARENCY: ALL CONTEXT-DEPENDENT 7\n",
      "AI GOVERNANCE REGIMES: SCENARIOS AND THEIR ASSESSMENT 8\n",
      "GENERIC AND CONTEXT DEPENDING OPPORTUNITIES AND POLICY LEVERS 9</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>2</page></document>\n",
      "<document><content>cultural norms and face resistance (Hu et al, 2019). In Europe there tem of trust’ within an EU regulatory framework. The strategy set\n",
      "is an ongoing discussion on the legal and ethical challenges posed out in the White Paper is to build and retain trust in AI. This needs</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>5</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "Table 1: Generic: concerns, opportunities and policy levers.\n",
      "Concerns/\n",
      "Description Policy/Regulatory lever\n",
      "opportunities\n",
      "Data governance (data preparation, data flows, data Regulate and/or stimulate European interoperability,</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>12</page></document>\n",
      "\u001b[1;32mrelevance\u001b[0m:\n",
      "yes\n",
      "==================================================\n",
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mllm_answer\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "\u001b[1;32mquestion\u001b[0m:\n",
      "What are the three main principles of the European Union's approach to ensuring \"trustworthy AI,\" and why are they significant?\n",
      "\u001b[1;32mcontext\u001b[0m:\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "laws and regulation. Some negative examples have been given order to achieve ‘trustworthy AI’, three components are necessary:</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>5</page></document>\n",
      "<document><content>(Teich & Tirias Research, 2018). Such examples are fuelling a va- trustworthy5. These policies culminated in the White Paper on AI\n",
      "riety of concerns about accountability, fairness, bias, autonomy, – A European Approach to Excellence and Trust (European Com-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>5</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "data for innovators, particularly in the business-to-business (B2B) 4 https://ec.europa.eu/digital-single-market/en/news/ethics-guidelines-trustworthy-ai\n",
      "or government-to-citizens (G2C) domains: e.g. by open access to</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>6</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "CONSISTENCY, RELIABILITY, AND TRANS-\n",
      "PARENCY: ALL CONTEXT-DEPENDENT\n",
      "Many AI innovations are still emerging and in experimental phase,\n",
      "and as such they still have to prove their consistency and relia-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>7</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "INTRODUCTION\n",
      "While a clear cut definition of Artificial Intelligence (AI) would be lives of EU citizens and bring major benefits to society and eco-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>4</page></document>\n",
      "<document><content>and mitigating risks and ensuring human rights and European va- with in the Health sector a system close to the current stepwise\n",
      "lues. approval processes for medicines and equipment.\n",
      "In addition, the following principles contribute to increase the po-\n",
      "sitive impact of AI applications:</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>21</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "in order to avoid biased decision and ensure fairness, we need to Current policy challenges and debates, besides geopolitical dis-</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>8</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "CONTENT\n",
      "EXECUTIVE SUMMARY 3\n",
      "INTRODUCTION\n",
      "CONSISTENCY, RELIABILITY, AND TRANSPARENCY: ALL CONTEXT-DEPENDENT 7\n",
      "AI GOVERNANCE REGIMES: SCENARIOS AND THEIR ASSESSMENT 8\n",
      "GENERIC AND CONTEXT DEPENDING OPPORTUNITIES AND POLICY LEVERS 9</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>2</page></document>\n",
      "<document><content>cultural norms and face resistance (Hu et al, 2019). In Europe there tem of trust’ within an EU regulatory framework. The strategy set\n",
      "is an ongoing discussion on the legal and ethical challenges posed out in the White Paper is to build and retain trust in AI. This needs</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>5</page></document>\n",
      "<document><content>A EUROPEAN APPROACH TO ARTIFICIAL INTELLIGENCE - A POLICY PERSPECTIVE\n",
      "Table 1: Generic: concerns, opportunities and policy levers.\n",
      "Concerns/\n",
      "Description Policy/Regulatory lever\n",
      "opportunities\n",
      "Data governance (data preparation, data flows, data Regulate and/or stimulate European interoperability,</content><source>./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf</source><page>12</page></document>\n",
      "('user', 'What are the three main principles of the European Union\\'s approach to ensuring \"trustworthy AI,\" and why are they significant?')\n",
      "('assistant', 'The three main principles of the European Union\\'s approach to ensuring \"trustworthy AI\" are:\\n\\n1. **Consistency**: AI systems must operate reliably across different contexts.\\n2. **Reliability**: AI applications should demonstrate dependable performance.\\n3. **Transparency**: The processes and decisions made by AI systems should be clear and understandable.\\n\\nThese principles are significant as they aim to build and retain trust in AI technologies, ensuring they align with human rights and European values while mitigating risks associated with AI deployment.\\n\\n**Source**\\n- ./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf (page 5)')\n",
      "\u001b[1;32mrelevance\u001b[0m:\n",
      "yes\n",
      "\u001b[1;32manswer\u001b[0m:\n",
      "The three main principles of the European Union's approach to ensuring \"trustworthy AI\" are:\n",
      "\n",
      "1. **Consistency**: AI systems must operate reliably across different contexts.\n",
      "2. **Reliability**: AI applications should demonstrate dependable performance.\n",
      "3. **Transparency**: The processes and decisions made by AI systems should be clear and understandable.\n",
      "\n",
      "These principles are significant as they aim to build and retain trust in AI technologies, ensuring they align with human rights and European values while mitigating risks associated with AI deployment.\n",
      "\n",
      "**Source**\n",
      "- ./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf (page 5)\n",
      "==================================================\n",
      "\n",
      "--- OUTPUTS ---\n",
      "\n",
      "Question: What are the three main principles of the European Union's approach to ensuring \"trustworthy AI,\" and why are they significant?\n",
      "Answer:\n",
      " The three main principles of the European Union's approach to ensuring \"trustworthy AI\" are:\n",
      "\n",
      "1. **Consistency**: AI systems must operate reliably across different contexts.\n",
      "2. **Reliability**: AI applications should demonstrate dependable performance.\n",
      "3. **Transparency**: The processes and decisions made by AI systems should be clear and understandable.\n",
      "\n",
      "These principles are significant as they aim to build and retain trust in AI technologies, ensuring they align with human rights and European values while mitigating risks associated with AI deployment.\n",
      "\n",
      "**Source**\n",
      "- ./data/A European Approach to Artificial intelligence - A Policy Perspective.pdf (page 5)\n",
      "Relevance: yes\n"
     ]
    }
   ],
   "source": [
    "### Execute the Graph\n",
    "\n",
    "# Set a `recursion_limit` to prevent indefinite loops during relevance checks\n",
    "\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "from langchain_opentutorial.messages import stream_graph, invoke_graph, random_uuid\n",
    "from langgraph.errors import GraphRecursionError\n",
    "\n",
    "# Create a configuration with recursion limit\n",
    "config = RunnableConfig(recursion_limit=20, configurable={\"thread_id\": random_uuid()})\n",
    "\n",
    "# Define the user question\n",
    "inputs = GraphState(question=\"What are the three main principles of the European Union's approach to ensuring \\\"trustworthy AI,\\\" and why are they significant?\")\n",
    "\n",
    "# Execute the graph\n",
    "try:\n",
    "    invoke_graph(app, inputs, config, [\"relevance_check\", \"llm_answer\"])\n",
    "    outputs = app.get_state(config)\n",
    "\n",
    "    # Access the values dictionary within StateSnapshot\n",
    "    output_values = outputs.values\n",
    "\n",
    "    # Display the results\n",
    "    print(\"\\n--- OUTPUTS ---\\n\")\n",
    "    print(\"Question:\", output_values[\"question\"])\n",
    "    print(\"Answer:\\n\", output_values[\"answer\"])\n",
    "    print(\"Relevance:\", output_values[\"relevance\"])\n",
    "except GraphRecursionError as recursion_error:\n",
    "    print(f\"GraphRecursionError: {recursion_error}\")\n",
    "except Exception as e:\n",
    "    print(f\"Unexpected Error: {e}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, if the ```relevance_check``` of the search result fails, the same query is repeatedly entered into the retrieve node.\n",
    "\n",
    "If the same query is repeatedly entered into the retrieve node, it will lead to the same search result, which will eventually lead to a recursion.\n",
    "\n",
    "To prevent possible recursion states, we set the maximum number of recursions (```recursion_limit```) and handle ```GraphRecursionError``` for error handling."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-rx8cAt47-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
